<!DOCTYPE html>
<!--
index.html
mldb.ai inc, 2014
Copyright (c) 2015 mldb.ai inc.  All rights reserved.
-->
<head>
    <title>REST API Interactive Documentation</title>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" / >
    <link rel="stylesheet" href="{{HTTP_BASE_URL}}/resources/css/bootstrap.min.css">
    <script src="{{HTTP_BASE_URL}}/resources/js/jquery-1.11.2.min.js" type="text/javascript"></script>
    <script src="{{HTTP_BASE_URL}}/resources/js/bootstrap.min.js" type="text/javascript"></script>
    <script src="{{HTTP_BASE_URL}}/resources/js/ace.js"  type="text/javascript" charset="utf-8"></script>

    <style>
        @import url(//fonts.googleapis.com/css?family=Roboto:400,700|Roboto+Slab:400,700);
        h2, h3  { font-family: "Roboto Slab"; font-weight: bold;}
        .row {margin-left: 15px; }
        .row > p { font-size: 16px; }
        code { color: #000000; }
        pre { padding: 0px 10px 0px 10px; }
        .ItemRow { margin: 5px 15px 5px 15px; }
        .ItemCol { padding: 0; }
        .ItemPanel { margin: 0px; }
        .ItemBody { padding: 5px; }
        .ItemMethod { margin: 0px; }
        .ItemTable { table-layout: fixed; }
        .ItemTableType { width: 80px; }
        .ItemTableFormat { width: 80px; }
        .TryURIParameter { width: 75px; }
        .string { color: blue; }
        .number { color: blue; }
        .boolean { color: blue; }
        .null { color: blue; }
        .key { color: black; }
        .ItemTableLength {
            width: 50px;
            text-align: right;
        }
        .JSONTextArea {
            color: #000000;
            font-size: 1em;
        }
    </style>


    <script type="text/javascript">
   
$(document).ready(function () {
    var allButtons = {};
    
    var allMethods = ["GET", "POST", "PUT", "DELETE"];

    var methodPanelClass = {
        "GET"    : "success",
        "POST"   : "info",
        "PUT"    : "warning",
        "DELETE" : "danger"
    };

    var methodButtonClass = {
        "GET"    : "success",
        "POST"   : "primary",
        "PUT"    : "warning",
        "DELETE" : "danger"
    };

    $.getJSON("{{HTTP_BASE_URL}}/doc/autodoc.json", function(data, status) {
        //showAlert(data["config"]);
        //showReferenceDocs(data["literate"]);
        showInteractiveDocs(data["routes"]);
        var hash = window.location.hash.slice(1);
        if(hash != "" && hash in allButtons) {
            allButtons[hash].trigger("click")[0].scrollIntoView();
        }
    });

    function showAlert(config) {         
        if (config != null) {
            if (config.alert != null && config.alert.contents != null) {
                var container = $('<div>')
                   .css({"width":"650px", "margin":"15px auto", "text-align": "center"})
                   .appendTo($("body"));
                var alert = $("<div>").addClass("alert alert-dismissable")
                   .html(config.alert.contents)
                   .prepend($('<button type="button" class="close" data-dismiss="alert" aria-hidden="true">').html("&times;"))
                   .addClass("alert-"+(config.alert.type == null ? "info" : config.alert.type))
                   .appendTo(container);
            }
        }
    }

    function onHeadingClick(event) {
        var $heading = $(event.currentTarget);
        var $group = $('.ItemGroup', $heading);
        var $item = $group.parents('.ItemPanel');

        var state = $group.data('state');


        if (state != 'closed') {
            var selector = "." + state;
            $(selector, $group).toggleClass('active', false).html(state);
            closePanel(state, $item);
            $group.data('state', 'closed');
        } else {
            $('.btn', $group).not(".btn-default")[0].click();
        }

        event.stopPropagation();
   }

   function onMethodButtonClick(event) {
        var $button = $(event.currentTarget);
        var $group = $button.parents('.ItemGroup');
        var $item = $group.parents('.ItemPanel');

        $button.toggleClass('active');
        var selected = $button.hasClass('active');

        var type = getButtonType($button);
        var state = $group.data('state');

        $button.html(type);

        if (state != 'closed') {
            // close the open panel first
            var selector = "." + state;
            $(selector, $group).toggleClass('active', false).html(state);
            closePanel(state, $item);
            state = 'closed';
        }

        if (selected) {
            // show selected panel
            openPanel(type, $item);
            state = type;
        }

        $group.data('state', state);

        event.stopPropagation();
   }

   // NOTE Try It buttom bacomes submit button when interactive part is expanded.
    function onTryButtonClick(event) {
        var $tryItButton = $(event.currentTarget);
        var $tryURI = $tryItButton.siblings('.TryURI');
        var $well = $tryItButton.parents('.InteractionWell');
        var $panel = $tryItButton.parents('.ItemMethod');
        var $jsonTextArea = $('.JSONTextArea', $well);

        var $jsonSyntaxError = $('.JSONSyntaxError');
        $jsonSyntaxError.remove();

        var id = $panel.data('id');
        var inputData = {};

        // clean up
        var $exchange = $('.Exchange', $well);
        $exchange.remove();
        // send the request

        var method = $tryURI.data("method")
        var uri = $tryURI.data("uri");
        var error = false;
        $('.TryURIParameter', $tryURI).each(function() {
            if (error) {
                return;
            }
            if ($(this).val().trim() == "") {
                error = true;
                return; // abort!
            }
            uri = uri.replace($(this).data("replacetext"), $(this).val());
        });
        var qs = $('.TryQueryString', $tryURI).val();
        if(qs){
            uri += "?"+qs;
        }

        if (error) {
            $('<span>')
                .addClass('JSONSyntaxError')
                .css('color', '#CC0000')
                .text("Missing a URL parameter")
                .insertAfter($tryItButton);
            return; // abort!
        }

        if (method === 'POST' || method === 'PUT') {
            try {
                var editor = ace.edit($jsonTextArea[0]);
                inputData = editor.getValue(); 
                $.parseJSON(inputData); //try a parse in case it throws
            } catch (e) {
                var $error = $('<span>')
                    .addClass('JSONSyntaxError')
                    .css('color', '#CC0000')
                    .text("JSON Syntax Error " + e.message)
                    .insertAfter($tryItButton);
                return; // abort!
            }
        }   
        submitIt(id, method, uri, inputData);
    }

    function replacement(match) {
        cls = 'number';
        if (/^"/.test(match)) {
            if (/:$/.test(match)) {
                cls = 'key';
            } else {
                cls = 'string';
            }
        } else if (/true|false/.test(match)) {
            cls = 'boolean';
        } else if (/null/.test(match)) {
            ls = 'null';
        }

        return '<span class="' + cls + '">' + match + '</span>';
    }


   // Return syntax highlighted HTML given plain JSON string.
    function syntaxHighlight(json) {
        json = json.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
        return json.replace(/("(\\u[a-zA-Z0-9]{4}|\\[^u]|[^\\"])*"(\s*:)?|\b(true|false|null)\b|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?)/g, replacement);
   }


    function getButtonType($button) {
        var type = null;
        $(['GET', 'POST', 'PUT', 'DELETE']).each(function(_, cls) {
            if ($button.hasClass(cls)) {
                type = cls;
                return false;
            }
        });
        return type;
    }

    function openPanel(type, $container) {
        $('.ItemBody', $container).css('display', 'block');

        var panel = '.' + 'panel' + '.' + type;
        $(panel, $container).css('display', 'block');
    }

    function closePanel(type, $container) {
        $('.ItemBody', $container).css('display', 'none');

        var panel = '.' + 'panel' + '.' + type;
        $(panel, $container).css('display', 'none');
    }



    function submitIt(id, method, uri, inputData) {  
        var renderRequest = function(id, jqXHR) {
            var $panel = null;
            $('.ItemMethod').each(function(index) {
                if (id === $(this).data('id')) {
                    $panel = $(this);
                }
            });

            var $well = $('.InteractionWell', $panel);
            var $interaction = $('<div>')
                .addClass('panel panel-default pull-right Exchange')
                .appendTo($well);   
            if (method == "POST" || method == "PUT") {
                $interaction.css("width", "49%");
            } else {
                $interaction.css("width", "100%");
            }

            var $exchange = $('<div>')
                .addClass('panel-body')
                .appendTo($interaction);

            var requestHTML = method + ' ' + uri + '<br>';
            var contentInHTML = '';

            if(method=="POST" || method=="PUT") {
                contentInHTML = 'Content-Type : application/json<br>';
            }

            var $request = $('<pre>')
                .html(requestHTML + contentInHTML)
                .appendTo($exchange);

            var messageHTML = jqXHR.status + ' ' + jqXHR.statusText + '<br>';
            var contentOutHTML = 'Content-Type : ' + jqXHR.getResponseHeader('Content-Type') + '<br>';
            var contentLengthHTML = 'Content-Length : ' + jqXHR.getResponseHeader('Content-Length') + '<br>';
            var responseTextHTML = jqXHR.responseText;
            if (jqXHR.getResponseHeader('Content-Type') == "application/json") {
                responseTextHTML = syntaxHighlight( JSON.stringify(
                    JSON.parse(responseTextHTML), undefined, 3
                ));
            }
            var $response = $('<pre>').css({"max-height":"400px","overflow":"scroll"})
                .html(messageHTML + contentOutHTML + contentLengthHTML + '<br>' + responseTextHTML)

            if (jqXHR.status >= 400) {
                $response.css("border-color", "#CC6060");
            }
            $response.appendTo($exchange);
        }

        var parameters = {
            url : uri,
            type : method,
            success : function(response, status, jqXHR){
                renderRequest(id, jqXHR);
            },
            error : function(jqXHR) {
                renderRequest(id, jqXHR);
            }
        };

        if (method=="POST" || method=="PUT") {
            parameters.data = inputData;
            parameters.contentType = "application/json";
        }

        $.ajax(parameters);
    }

    function processData(data) {
        var endpoints = []; 
        var lastURI = null; 

        for (var i = 0, all = data.length; i < all; i++) {
            var row = data[i]
            if(row.length > 2)
            {
                endpoints.push(row);
            }
            else
            {
                var route = row[0].split(" ")
                var method = route[0].trim();
                var uri = route[1].trim();

                var details = row[1];
                var docstring = (!details) && (!details.docstring) ? '' : details.docstring;
                var role = (!details) && (!details.required_role) ? '' : details.required_role;

                var input = (!details) ? null : details.in; 
                var output = (!details) ? null : details.out; 
                var qs = (!details) ? null : details.querystring; 

                if (uri === lastURI) {
                    // augment endpoint
                    var last = endpoints.length - 1;
                    endpoints[last].methods.push(method);
                    endpoints[last].docs.push(docstring);
                    endpoints[last].roles.push(role);
                    endpoints[last].inputs.push(input);
                    endpoints[last].outputs.push(output);
                    endpoints[last].qs.push(qs);
                    endpoints[last].ids.push(i);
                } else {
                    // new endpoint
                    var item = {
                        uri     : uri,
                        methods : [ method ],
                        docs    : [ docstring ],
                        roles   : [ role ],
                        inputs  : [ input ],
                        outputs : [ output ],
                        qs      : [ qs ],
                        ids     : [ i ]
                    };
                    endpoints.push(item);
                }
                lastURI = uri;
            }
        }

        return endpoints;
    }


    function showInteractiveDocs(routes) {
        var $body = $('body');
        var $container = $('<div>').addClass('container');
        $container.appendTo($body);

        var $pageHeader = $('<div>')
            .addClass('page-header')
            .appendTo($container);
        var $h2 = $('<h2>')
            .text('REST API Interactive Documentation')
            .appendTo($pageHeader);

        var listing = processData(routes);
        //listing.sort(function(a,b){ return a.uri.localeCompare(b.uri); });

        for (var i = 0, all = listing.length; i < all; i++) {
            if(listing[i].uri == null)
            {
                $('<div>')
                    .addClass('row')
                    .appendTo($container)
                    .html(listing[i]);
                continue;
            }
            var endpoint = listing[i]; 
            var uri = endpoint.uri;
            var methods = endpoint.methods;
            var docs = endpoint.docs;
            var ids = endpoint.ids;

            var $row = $('<div>')
                .addClass('row ItemRow')
                .appendTo($container);

            var $col = $('<div>')
                .addClass('col-xs-12 ItemCol')
                .appendTo($row);

            var $panel = $('<div>')
                .addClass('panel panel-default ItemPanel')
                .attr('id', 'item' + i)
                .appendTo($col);

            var $heading = $('<div>')
                .addClass('panel-heading ItemHeading')
                .appendTo($panel);

            $heading.click(onHeadingClick);               

            var $body = $('<div>')
                .addClass('panel-body ItemBody')
                .css('display', 'none')
                .appendTo($panel);

            // button group           
            var $group = $('<div>')
                .addClass('btn-group btn-group-xs ItemGroup')
                .data('state', 'closed')
                .appendTo($heading);

            // URI
            var $uri = $('<strong>')
                .text(' ' + uri)
                .appendTo($heading);

            var method, _i, _len;

            for (_i = 0, _len = allMethods.length; _i < _len; _i++) {
                method = allMethods[_i];
                var enabled = (methods.indexOf(method) != -1);
                var methodButton = createMethodButton(method, enabled)
                $group.append(methodButton);  
                allButtons[method+":"+uri] = methodButton;
                if (enabled) {
                    $body.append(createPanel(method, endpoint));
                }
            }
        }

        $("<div class='text-center' style='color: #aaa'>").text("© Copyright 2010-2014 mldb.ai inc. All Rights Reserved").appendTo($container);
   }


    function showReferenceDocs(literate) {
        if(literate.length == 0) {
            return;
        }

        var $body = $('body');

        var $container = $('<div>').addClass('container');
        $container.appendTo($body);

        var $pageHeader = $('<div>')
            .addClass('page-header')
            .appendTo($container);
        var $h2 = $('<h2>')
            .text('Reference Documentation')
            .appendTo($pageHeader);

        var ul = $("<ul>");
        for (var i = 0; i < literate.length; i++) {
            ul.append(
                $("<li>").append(
                    $("<a>")
                        .attr("href", "literate/"+literate[i].filename)
                        .text( literate[i].name) 
                )
            );
        }

        $container.append(ul);
    }
 
    function createMethodButton(method, enabled) {
        var style = 'btn-default';

        if (enabled) {
            style = "btn-" + methodButtonClass[method];
        }

        var classes = 'btn ' + style + ' ' + method;

        var $button = $('<div>')
            .addClass(classes)
            .attr('type', 'button')
            .html(method);

        if (!enabled) {
            $button.attr('disabled', 'disabled');     
            $button.css('cursor', 'default');     
        }   

        $button.click(onMethodButtonClick);     

        return $button;
    }


   // Single endpoint panel for a given method method (GET, POST, PUT, DELETE).
   function createPanel(method, endpoint) {
        var index = endpoint.methods.indexOf(method);
        var description = endpoint.docs[index];
        var role = endpoint.roles[index];
        var input = endpoint.inputs[index];
        var output = endpoint.outputs[index];
        var qs = endpoint.qs[index];
        var id = endpoint.ids[index];

        var properties = output.properties;
        var required = output.required;

        // main panel container
        var $panel = $('<div>')
            .addClass(method+ ' ItemMethod panel panel-' + methodPanelClass[method])
            .css('display', 'none')
            .data('id', id);

        // heading with description (and role inside)
        var $heading = $('<div>')
            .addClass('panel-heading')
            .appendTo($panel);

        var $description = $('<div>').addClass('clearfix').appendTo($heading);
        if (description == null) {
            description = "";
        }
        var $doc = $('<div>')
            .addClass('pull-left')
            .html('&nbsp;' + description)
            .appendTo($description);
        var $label = $('<span>')
            .addClass('label label-' +  methodButtonClass[method])
            .text(method).prependTo($doc);

        var $body = $('<div>')
            .addClass('panel-body')
            .appendTo($panel);

        // input reference
        if (!$.isEmptyObject(input.properties) || !$.isEmptyObject(input.items)) {
            $('<span>').addClass('label label-default').text('INPUT').appendTo($body);
            $body.append(createTable(input)); 
        }

        // output reference
        if (!$.isEmptyObject(output.properties) || !$.isEmptyObject(output.items)) {
            $('<span>').addClass('label label-default').text('OUTPUT').appendTo($body);
            $body.append(createTable(output));            
        }

        // query-string reference
        var has_qs = false;
        if (!$.isEmptyObject(qs.properties) || !$.isEmptyObject(qs.items)) {
            $('<span>').addClass('label label-default').text('QUERY STRING').appendTo($body);
            $body.append(createTable(qs));            
            has_qs = true;
        }

        // interactive section
        var $well = $('<div>')
            .addClass('well well-sm clearfix InteractionWell')
            .data('mode', 'collapsed')
            .appendTo($body);

        var $tryURI = $('<div>')
            .addClass('pull-left TryURI')
            .data('uri', endpoint.uri)
            .data("method", method)
            .appendTo($well);

        var $code = $('<span>')
            .css({"font-family": "Courier"})
            .appendTo($tryURI);              

        var segments = endpoint.uri.split("/");
        var accumulator= method + " ";
        for(var i=0; i<segments.length; i++) {
            if (segments[i] =="") {
                continue;
            }
            if (segments[i][0] != "<") {
                accumulator += "/"+segments[i];
            } else {
                $('<span>')
                    .text(accumulator+"/")
                    .appendTo($code);
                accumulator = "";
                $("<input>")
                   .attr('type', 'text')
                   .addClass("TryURIParameter")
                   .attr('placeholder', segments[i])
                   .data("replacetext", segments[i])
                   .appendTo($code);
            }
        }

        if(accumulator != "") {
            $('<span>').text(accumulator).appendTo($code);
        }
        if(has_qs){
            $('<span>').text("?").appendTo($code);
            $("<input>")
                .attr('type', 'text')
                .addClass("TryQueryString")
                .attr('key=value', segments[i])
                .appendTo($code);
        }

        var $tryButton = $('<button>')
            .addClass('btn btn-default btn-xs pull-left TryItButton')
            .attr({'type' : 'button', 'tabindex' : 0})
            .css({"margin": "auto 5px"})
            .text('Send ' + method + ' request')
            .appendTo($well);

        if( window.location.origin ==  "http://mldb.ai"      || 
            window.location.origin ==  "http://docs.mldb.ai" || 
            window.location.origin == "https://docs.mldb.ai"   )
        {
            $tryButton.click(function(){
                alert("Interactive REST calls only work with a running MLDB instance, not on docs.mldb.ai\n\n" +
                     "You can follow the 'Running MLDB' instructions to run your own MLDB instance.");
            });
        }
        else
        {
            $tryButton.click(onTryButtonClick);
            $('<br><br>').appendTo($well);   

            if (method == "POST" || method == "PUT") {
                var starterJSON = createStarterJSON(input, 0); 
                var $jsonTextArea = $('<div>')
                    .addClass('form-control pull-left JSONTextArea')
                    .css({'height': "400px", "width": "49%"});
                $jsonTextArea.appendTo($well);

                var editor = ace.edit($jsonTextArea[0]);
                editor.setValue(starterJSON);
                editor.clearSelection();
                var session = editor.getSession();
                session.setMode("ace/mode/json");
                session.setTabSize(2);
            }
        }

        return $panel;
    }

    function createStarterJSON(schema, level) {
        var result = "";
        switch (schema.type) {
            case "integer":
                result += "1";
                break;
            case "number":
                result += "1";
                break;
            case "boolean":
                result += "true";
                break;
            case "string":
                if (schema.enum) {
                    result += '"' +  schema.enum[0] + '"';
                }
                else if(schema.patternName == "ISO 8601 date-time") {
                    result += '"' + (new Date()).toISOString() + '"';
                }
                else {
                    result += '"string"';
                }
                
                break;
            case "array":
                if(schema.items == null)
                    result += "[ ]";
                else
                    result += "[ " + createStarterJSON(schema.items, level) + " ]";
                break;
            case "object":
            default:
                result += "{\n";
                for (var p in schema.properties) {
                    if (schema.properties.hasOwnProperty(p)) {
                        for (var i=0; i<=level; i++) {
                            result+="  ";
                        }
                        result += '"'+p+'": ';
                        result += createStarterJSON(schema.properties[p], level+1);
                        result += ",\n";
                    }
                }
                result = result.replace(/,\n$/,"\n");
                for (var i=0; i<level; i++) result+="  ";
                result += "}";
                break;
        }
        return result;
    }
   
    function createTable(io) {
        var $table = $('<table>').addClass('table table-condensed ItemTable');

        var $trhead = $('<tr>').appendTo($table);

        $('<th>').text('Key').css("width", "250px").appendTo($trhead);
        $('<th>').text('Type').css("width", "200px").appendTo($trhead);
        $('<th>').text('Description').appendTo($trhead);

        addSchemaToTable($table, io, 0, "[root]", false, false);
        return $table;
   }

    function addSingleSchemaRow(table, level, type, name, details, optional, desc) {
        if(optional) {
            name += " (optional)";
        }
        if (desc == null) {
            desc = ""; 
        }
        table.append( 
            $('<tr>').append(
                $('<td>').text(name).css("padding-left", (5+level*20) + "px"),
                $('<td>').text(type),
                $('<td>').html(desc)
            )
        );
    }

   function addSchemaToTable(table, schema, level, name, isArray, optional) {
        var prefix = isArray? "array of " : "";
        var details = [];
        switch (schema.type) {
            case "integer":
            case "number":
            case "boolean":
            case "string":
                if(schema.enum) {
                    details.push("one of: " + schema.enum.join(", "));
                }
                else if(schema.patternName) {
                    details.push("pattern: " + schema.patternName);
                }
                else if(schema.maxLength) {
                    details.push("max length: " + schema.maxLength);
                }
                addSingleSchemaRow(table, level, prefix+schema.type, name, 
                                   details, optional, schema.description)
                break;
            case "array":
                if(schema.items == null)
                    addSingleSchemaRow(table, level, prefix+schema.type, name, 
                                   details, optional, schema.description)
                else
                    addSchemaToTable(table, schema.items, level, name, true, optional)
                break;
            case "object":
            default:
                addSingleSchemaRow(table, level, prefix+"object", name, 
                                   details, optional, schema.description)
                for (var p in schema.properties) {
                    if (schema.properties.hasOwnProperty(p)) {
                        var subSchema = schema.properties[p];
                        var subOptional = schema.required.indexOf(p) < 0;
                        addSchemaToTable(table, subSchema, level+1, p, false, subOptional);
                }
            }
            break;
        }
    }
});

   </script>
</head>
<body>
</body>
</html>
